import * as fs from 'fs';
import * as pth from 'path';
import { CommonUtils } from './commonutils';
import { Observable } from 'rxjs';

const PATH_SEPARATOR = pth.sep;
export const Fs = fs;
export const Path = pth;
export class FSUtils {
    /**
     * 判断路径是否为http路径
     * @param path 传入的路径
     */
    static isHttp(path: string) {
        if (path && (path.toLowerCase().startsWith('http://') || path.toLowerCase().startsWith('https://'))) {
            return true;
        }
        return false;
    }
    /**判断文件是否存在 */
    static fileExists(path: string): boolean {
        if (path && fs.existsSync(path)) {
            return true;
        }
        return false;
    }
    /**判断目录是否存在 */
    static directoryExists(dir: string) {
        let stat: fs.Stats;
        try {
            if (dir) {
                stat = fs.statSync(dir);
            }
        } catch (e) {
        }
        if (stat) {
            if (stat.isDirectory()) {
                return true;
            } else if (stat.isFile()) {
                throw new Error(`${dir}不是一个有效的目录路径`);
            }
        }
        return false;
    }
    /**
     * 创建目录
     * @param path 要创建的目录路径
     */
    static mkdirSync(/*String*/path) {
        let resolvedPath = path.split(PATH_SEPARATOR)[0];
        path.split(PATH_SEPARATOR).forEach((name) => {
            if (!name || name.substr(-1, 1) === ':') {
                return;
            }
            resolvedPath += PATH_SEPARATOR + name;
            if (!this.directoryExists(resolvedPath)) {
                fs.mkdirSync(resolvedPath);
            }
        });
    }
    /**
     * 读取本地文件
     * @param localPath 本地文件路径
     */
    static readLocalFile(localPath: string) {
        return new Promise((resolve, reject) => {
            fs.readFile(localPath, null, (error, data) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(data);
                }
            });
        });
    }
    /**
     * 查找指定目录及其子目录下的文件
     * @param path 要查找的目录
     * @param pattern 要查找的文件筛选器
     * @param recoursive 标识是否包含子目录，默认为：true
     */
    static findFiles(path: string, pattern?: RegExp, recoursive?: boolean): string[] {
        recoursive = typeof(recoursive) === 'undefined' ? true : recoursive;
        return this.findSync(path, pattern, recoursive);
    }
    /**
     * 异步查找指定目录及其子目录下的文件
     * @param path 要查找的目录
     * @param pattern 要查找的文件筛选器
     * @param recoursive 标识是否包含子目录，默认为：true
     */
    static async findFilesAsync(path: string, pattern?: RegExp, recoursive?: boolean) {
        recoursive = typeof(recoursive) === 'undefined' ? true : recoursive;
        return await new Promise<string[]>((resolve, reject) => {
            this.findAsync(path, pattern, recoursive, (error, data) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(data);
                }
            });
        });
    }
    /**
     * 同步写入文件
     * @param path 文件路径
     * @param content 文件内容数据
     * @param overwrite 如果文件存在是否覆盖存在的文件
     * @param attr 文件属性
     */
    static writeFileTo(path: string, content: Buffer, overwrite: boolean, attr: number) {
        if (fs.existsSync(path)) {
            if (!overwrite) {
                return false; // cannot overwrite
            }

            const stat = fs.statSync(path);
            if (stat.isDirectory()) {
                return false;
            }
        }
        const folder = pth.dirname(path);
        if (!fs.existsSync(folder)) {
            this.mkdirSync(folder);
        }

        let fd;
        try {
            fd = fs.openSync(path, 'w', 438); // 0666
        } catch (e) {
            fs.chmodSync(path, 438);
            fd = fs.openSync(path, 'w', 438);
        }
        if (fd) {
            try {
                fs.writeSync(fd, content, 0, content.length, 0);
            } catch (e) {
                throw e;
            }
            finally {
                fs.closeSync(fd);
            }
        }
        fs.chmodSync(path, attr || 438);
        return true;
    }
    /**
     * 异步写入文件
     * @param path 文件路径
     * @param content 文件内容数据
     * @param overwrite 如果文件存在是否覆盖存在的文件
     * @param attr 文件属性
     * @param callback 回调方法
     */
    static writeFileToAsync(path: string, content: Buffer, overwrite: boolean,
        callback: (error: any, state: boolean) => void, attr?: number) {
        const _callback = (error: any, state?: boolean) => {
            if (callback) {
                callback(error, state);
            }
        };
        fs.exists(path, (exists) => {
            if (exists && !overwrite) {
                return _callback(new Error(`文件${path}已存在`), false);
            }

            fs.stat(path, (err, stat) =>  {
                if (exists && stat.isDirectory()) {
                    return _callback(new Error(`${path}不是有效的文件路径`), false);
                }

                const folder = pth.dirname(path);
                fs.exists(folder, _exists => {
                    if (!_exists) {
                        this.mkdirSync(folder);
                    }

                    fs.open(path, 'w', 438, (err1, fd1) => {
                        if (err1) {
                            fs.chmod(path, 438, () => {
                                fs.open(path, 'w', 438, (err2, fd2) => {
                                    fs.write(fd1, content, 0, content.length, 0, () => {
                                        fs.close(fd2, () => {
                                            fs.chmod(path, attr || 438, () => {
                                                _callback(null, true);
                                            });
                                        });
                                    });
                                });
                            });
                        } else {
                            if (fd1) {
                                fs.write(fd1, content, 0, content.length, 0, () => {
                                    fs.close(fd1, () => {
                                        fs.chmod(path, attr || 438, () => {
                                            _callback(null, true);
                                        });
                                    });
                                });
                            } else {
                                fs.chmod(path, attr || 438, () => {
                                    _callback(null, true);
                                });
                            }
                        }
                    });
                });
            });
        });
    }
    /**
     * 读取文本内容
     * @param filePath 文件路径
     * @param callback 回调处理
     */
    static readTextAsync(filePath: string, callback: (error: any, data: string) => void) {
        const _callback = (error, data?) => {
            if (callback) {
                callback(error, data);
            }
        };
        fs.readFile(filePath, (error, data) => {
            if (error) {
                _callback(error);
            } else {
                try {
                    // 解码buffer数据为字符串
                    const str = CommonUtils.decodeBufferToString(data);
                    _callback(null, str);
                } catch (ex) {
                    _callback(ex);
                }
            }
        });
    }
    /**
     * 查找指定目录下的文件
     * @param dir 目录路径
     * @param pattern 匹配正则表达式
     * @param recoursive 是否获取子目录的文件
     */
    private static findSync(dir: string, pattern: RegExp, recoursive: boolean): string[] {
        const tmpFiles = [];
        let tmpDirs = [];
        try {
            fs.readdirSync(dir).forEach((file) => {
                const path = pth.join(dir, file);
                try {
                    const state = fs.statSync(path);
                    if (!pattern || pattern.test(path)) {
                        if (state.isDirectory()) {
                            tmpDirs.push(path + PATH_SEPARATOR);
                        } else if (state.isFile()) {
                            tmpFiles.push(path);
                        }
                    }
                    if (state.isDirectory() && recoursive) {
                        tmpDirs = tmpDirs.concat(this.findSync(path, pattern, recoursive));
                    }
                } catch {}
            });
        } catch (ex) {
        }
        return tmpDirs.concat(tmpFiles);
    }
    /**
     * 异步查找指定目录下的文件
     * @param dir 目录路径
     * @param pattern 匹配正则表达式
     * @param recoursive 是否获取子目录的文件
     * @param callback 回调处理
     */
    private static findAsync(dir: string, pattern: RegExp, recoursive: boolean, callback: (error: any, files: string[]) => void): void {
        const _callback = (error: any, files: string[]) => {
            if (callback) {
                callback(error, files);
            }
        };
        fs.readdir(dir, (error, files) => {
            if (error) {
                _callback(error, null);
            } else {
                if (files && files.length > 0) {
                    const tempFiles = [];
                    let tempDirs = [];
                    const enumOpera = (index) => {
                        if (index === files.length) {
                            _callback(null, tempDirs.concat(tempFiles));
                            return;
                        }
                        const fileName = files[index];
                        const path = pth.join(dir, fileName);
                        fs.stat(path, (_error, stat) => {
                            if (!_error) {
                                if (!pattern || pattern.test(path)) {
                                    if (stat.isFile()) {
                                        tempFiles.push(path);
                                    } else if (stat.isDirectory()) {
                                        tempDirs.push(path + PATH_SEPARATOR);
                                    }
                                }
                                if (stat.isDirectory() && recoursive) {
                                    this.findAsync(path, pattern, recoursive, (__error, __files) => {
                                        if (!__error) {
                                            tempDirs = tempDirs.concat(__files);
                                        }
                                        enumOpera(index + 1);
                                    });
                                    return;
                                }
                            }
                            enumOpera(index + 1);
                        });
                    };
                    enumOpera(0);
                } else {
                    _callback(null, []);
                }
            }
        });
    }
}
